#include "ultraMips.h"
#include "params.h"
#include "vpi_user.h"

/***** Fetch variables *****/
static int fetch_mem_count = 0;
static int fetch_pc = 0;
static int fetch_read_ptr = 0;
static int fetch_write_ptr = 0;
static int fetch_pending = 0;
static int fetch_recovering = 0;
static int odd_jump = 0;
static int branch_queue[16];
/***************************/

void breakup_inst(int , inst* );
void dec_ex_wb(inst* );

void ultramips()
{
	fetch();	
	issue();	
	reg();
	execute();
	memB();
	write_back();
	commit();	
}

void clear_input_ports()
{
	//clears all input port
	ports_i.reset = 0;
	ports_i.inst_i_valid = 0;
	ports_i.inst_i_line = 0;
	
	ports_i.mem_i_line = 0;
	ports_i.mem_i_dest_m = 0;
	ports_i.mem_i_ctrl_m = 0;
	ports_i.mem_i_line_valid_m = 0;
}

void fetch()
{
	/* Resets valids to 0 */
	branch_dep1 = 0;
	branch_dep0 = 0;
	
	mem_tag1 = 0;
	mem_tag0 = 0;

	/* Reads input line if not recovering */
	if (!fetch_recovering) {
		/**********Check Endian Issue**********/
		inst1 = (int) ports_i.inst_i_line;
		inst0 = (int) (ports_i.inst_i_line >> 32);
	}

	/* Sets recovering if needed - needs to be after line input read */
	if (full)
		fetch_recovering = 1;
	
	/* Get formatted instructions */
	inst instA, instB;
	breakup_inst(inst1, &instA);
	breakup_inst(inst0, &instB);

	/* Determine if instructions are valid */
	if (!ports_i.inst_i_valid) {
		inst_valid1 = 0;
		inst_valid0 = 0;
	} else if (odd_jump) {
		inst_valid1 = 0;
		inst_valid0 = 1;
		odd_jump = 0;
		fetch_pending = 0;
	} else {
		inst_valid1 = 1;
		inst_valid0 = 1;
		fetch_pending = 1;
	}

	/* Update read ptr for nt fifo if taken */
	if (branch_decision == 1) {
		if (fetch_read_ptr == 15)
			fetch_read_ptr = 0;
		else
			fetch_read_ptr++;
	}
		
	/* Set branch tag info */
	int old_pc_odd = (fetch_pc & 0x7) - 4;
	int old_pc_even = (fetch_pc & 0x7);

	branch_count1 = fetch_write_ptr;
	if (instA.opcode == 0x4 && inst_valid1) {
		inst_valid0 = 0;
		branch_queue[fetch_write_ptr] = old_pc_odd;
		if (fetch_write_ptr == 15)
			fetch_write_ptr = 0;
		else
			fetch_write_ptr++;
	}
	branch_dep1 = fetch_write_ptr == fetch_read_ptr;

	branch_count0 = fetch_write_ptr;
	if (instB.opcode == 0x4 && inst_valid0) {
		branch_queue[fetch_write_ptr] = old_pc_even;
		if (fetch_write_ptr == 15)
			fetch_write_ptr = 0;
		else
			fetch_write_ptr++;
	}
	branch_dep0 = fetch_write_ptr == fetch_read_ptr;

	/* Set mem tag info */
	mem_count1 = fetch_mem_count;
	if (instA.opcode >= 0x20 && inst_valid1) {
		mem_tag1 = 1;
		if (fetch_mem_count == 15)
			fetch_mem_count = 0;
		else
			fetch_mem_count++;
	}

	mem_count0 = fetch_mem_count;
	if (instB.opcode >= 0x20 && inst_valid0) {
		mem_tag0 = 1;
		if (fetch_mem_count == 15)
			fetch_mem_count = 0;
		else
			fetch_mem_count++;
	}

	/* Set new pc */
	if (!full)
		fetch_pc = fetch_pc + 8;

	if (instB.opcode == 0x4 && inst_valid0) {
		fetch_pc = fetch_pc + instB.imm_extended;
		if ((fetch_pc & 4) == 4)
			odd_jump = 1;
	}

	if (instB.opcode == 0x2 && inst_valid0) {
		fetch_pc = (inst0 & 0x3ffffff) << 2;
		if ((fetch_pc & 4) == 4)
			odd_jump = 1;
	}

	if (instA.opcode == 0x4 && inst_valid1) {
		fetch_pc = fetch_pc + instA.imm_extended;
		if ((fetch_pc & 4) == 4)
			odd_jump = 1;
	}

	if (instA.opcode == 0x2 && inst_valid1) {
		fetch_pc = (inst1 & 0x3ffffff) << 2;
		if ((fetch_pc & 4) == 4)
			odd_jump = 1;
	}

	/* If not taken, set pc and flush */
	if (branch_decision == 2) {
		fetch_pc = branch_queue[fetch_read_ptr];

		fetch_read_ptr = 0;
		fetch_write_ptr = 0;

		inst_valid1 = 0;
		inst_valid0 = 0;

		branch_dep1 = 0;
		branch_dep0 = 0;

		mem_tag1 = 0;
		mem_tag0 = 0;		
	}

	/* Output addr to memory and update pending and recovering if needed */
	ports_o.inst_o_addr = fetch_pc;
	ports_o.inst_o_valid = !fetch_pending && !fetch_recovering && !full;

	if (ports_o.inst_o_valid)
		fetch_pending = 1;

	if (fetch_recovering && !full)
		fetch_recovering = 0;
}

void issue()
{

//call break_inst and put the respective fields in the variables defined
inst *insta,*instb;
insta= (inst*)malloc(sizeof(inst));
instb= (inst*)malloc(sizeof(inst));
breakup_inst(inst1,insta);
breakup_inst(inst0,instb);

int rs1,rs0,rt1,rt0,rd1,rd0;

rs1 =insta->rs;
rt1 =insta->rt;
rd1 =insta->rd;
rs0 =instb->rs;
rt0 =instb->rt;
rd0 =instb->rd;


int i,j; // loop variables

/* update scoreboard : from fetch and writeback*/

if (rd1_valid==1) 
	scoreboard[rd1]=1;

if (rd0_valid==1) 
	scoreboard[rd0]=1;


/* prepare instructions for issue from current queue status*/

// generating mem_valid
for (i=0;i<16;i++)
	{if (mem_count[i]==mem_counter && mem_tag[i]==1)
		mem_valid[i]=1;
	else
		mem_valid[i]=0;
	}
	
// generating inputs to the priority encoder A
for (i=0;i<16;i++)
	{if (inst_valid[i]==1 && branch_tag[i]==0 && mem_tag[i]==0 && rs_tag[i]==0 && rt_tag[i]==0)
		pe_A[i]=1;
	else
		pe_A[i]=0;
	}

found_qaddrA =0;


// finding qaddress for the instruction to be issued in pipeline A and issuing instructionA
for (i=0;i<16;i++)
	{if (found_qaddrA==0)
		{if (pe_A[i]==1)
			{found_qaddrA=1;
			qaddrA=i;
			}
		else
			found_qaddrA=0;
		}
	}


if (found_qaddrA==1)
	{instA_issue=inst_[qaddrA];
	branch_countA_issue=branch_count[qaddrA];
	}
else
	{instA_issue=0;
	branch_countA_issue=0;
	}

// generating inputs to the priority encoder B
for (i=0;i<16;i++)
	{if (inst_valid[i]==1 && branch_tag[i]==0 && mem_valid[i]==1 && rs_tag[i]==0 && rt_tag[i]==0)
		pe_B[i]=1;
	else
		pe_B[i]=0;
	}

found_qaddrB =0;
// finding qaddress for the memory instruction to be issued in pipeline B and issuing memory instruction in pipe B
for (i=0;i<16;i++)
	{if (found_qaddrB==0)
		{if (pe_B[i]==1)
			{found_qaddrB=1;
			qaddrB=i;
			}
		else
			found_qaddrB=0;
		}
	}

if (found_qaddrB==1)
	{instB_issue=inst_[qaddrB];
	branch_countB_issue=branch_count[qaddrB];
	}
else // if no memory instruction to go then check if any non-memory instruction (apart from the one issued in pipeA) is ready to go
	{
	for (i=0;i<16;i++)
		{if (inst_valid[i]==1 && branch_tag[i]==0 && mem_tag[i]==0 && rs_tag[i]==0 && rt_tag[i]==0)
			pe_B[i]=1;
		else
			pe_B[i]=0;
		}

	found_qaddrB =0;

// finding qaddress for the non-mem instruction to be issued in pipeline B and issuing the non-mem instruction in pipe B
	for (i=0;i<16;i++)
		{if (found_qaddrB==0 || found_qaddrB==1)
			{if (pe_B[i]==1)
				{found_qaddrB=found_qaddrB+1;
				if (found_qaddrB==2)
					qaddrB=i;
				}
			}
		}
	if (found_qaddrB==2)
		{instB_issue=inst_[qaddrB];
		branch_countB_issue=branch_count[qaddrB];
		}
	else
		{instB_issue=0;
		branch_countB_issue=0;
		found_qaddrB=0;
		}
	}

// generating the full signal
issue_inst_B = instB_issue;
issue_inst_A = instA_issue;

j=0;
for (i=0;i<16;i++)
	{j=j+occupied_bit[i];
	}
if (j==14)
	full=1;

/*generating rs, rt tags, filling the queue */

// write address generation//
found_wr_addr1=0;

for (i=0;i<16;i++)
	{if (found_wr_addr1==0)
		{if (occupied_bit[i]==0)
			{found_wr_addr1=1;
			wr_addr1=i;
			}
		else
			found_wr_addr1=0;
		}
	}
	
found_wr_addr0=0;

for (i=0;i<16;i++)
		{if (found_wr_addr0==0 || found_wr_addr0==1)
			{if (occupied_bit[i]==0)
				{found_wr_addr0=found_wr_addr0+1;
				if (found_wr_addr0==2)
					wr_addr0=i;
				}
			}
		}		
	
// generating rs,rt tags

if (rs1_valid == 1 && (rs1!=rd1 || rs1!=rd0))
	rs1_tag_temp=scoreboard[rs1];
else
	rs1_tag_temp=0;

if (rt1_valid == 1 && (rt1!=rd1 || rt1!=rd0))
	rt1_tag_temp=scoreboard[rt1];
else
	rt1_tag_temp=0;

if (rs0_valid == 1 && (rs0!=rd1 || rs0!=rd0))
	rs0_tag_temp=scoreboard[rs0];
else
	rs0_tag_temp=0;

if (rt0_valid == 1 && (rt0!=rd1 || rt0!=rd0))
	rt0_tag_temp=scoreboard[rt0];
else
	rt0_tag_temp=0;
	
//filling the queue

inst_[wr_addr1]=inst1;
inst_[wr_addr0]=inst0;

inst_valid[wr_addr1]=inst_valid1;
inst_valid[wr_addr0]=inst_valid0;

branch_tag[wr_addr1]=branch_dep1;
branch_tag[wr_addr0]=branch_dep0;
temp_branchtag[wr_addr1]=branch_dep1;
temp_branchtag[wr_addr0]=branch_dep0;

mem_tag[wr_addr1]=mem_tag1;
mem_tag[wr_addr0]=mem_tag0;

branch_count[wr_addr1]=branch_count1;
branch_count[wr_addr0]=branch_count0;

mem_count[wr_addr1]=mem_count1;
mem_count[wr_addr0]=mem_count0;

rs_tag[wr_addr1]=rs1_tag_temp;
rs_tag[wr_addr0]=rs0_tag_temp;
temp_rstag[wr_addr1]=rs1_tag_temp;
temp_rstag[wr_addr0]=rs0_tag_temp;

rt_tag[wr_addr1]=rt1_tag_temp;
rt_tag[wr_addr0]=rt0_tag_temp;
temp_rttag[wr_addr1]=rt1_tag_temp;
temp_rttag[wr_addr0]=rt0_tag_temp;

occupied_bit[wr_addr1]=inst_valid1;
occupied_bit[wr_addr0]=inst_valid0;
temp_occupied_bit[wr_addr1]=inst_valid1;
temp_occupied_bit[wr_addr0]=inst_valid0;

/*updating the queue with the recently issued instructions and the feedback signals from the next stages*/
if (mem_tag[qaddrB] == 1 && found_qaddrB == 1)
	mem_counter=mem_counter+1;

if (found_qaddrA==1)
	inst_valid[qaddrA]=0; 

if (found_qaddrB==1 || found_qaddrB==2 )
	inst_valid[qaddrB]=0;


for (i=0;i<16;i++)
	{occupied_bit[i]= occupied_bit[i] & temp_occupied_bit[i];
	rs_tag[i]= rs_tag[i] & temp_rstag[i];
	rt_tag[i]=rt_tag[i] & temp_rttag[i];
	branch_tag[i]=branch_tag[i] & temp_branchtag[i];
	}

for (i=0;i<16;i++)
	{temp_occupied_bit[i]= occupied_bit[i];
	temp_rstag[i]= rs_tag[i];
	temp_rttag[i]=rt_tag[i];
	temp_branchtag[i]=branch_tag[i];
	}


//------------------------write the below statements in any of the subsequent stage------------------------------------------//
// updating occupied bit
if (found_qaddrA==1)
	temp_occupied_bit[qaddrA]=0; 

if (found_qaddrB==1 || found_qaddrB==2 )
	temp_occupied_bit[qaddrB]=0;

if (branch_decision == 2)
	{for(i=0;i<15;i++)
		{if (branch_count[i]==br_count+1)
			temp_occupied_bit[i]=0;
		}
	}
//updating rs_tag, rt_tag

if (rd1_broadcast_v==1)
	{for (i=0;i<16;i++)
		{
		if(rs[i]==rd1_broadcast)
			temp_rstag[i]=0;
		if(rt[i]==rd1_broadcast)
			temp_rttag[i]=0;
		}
	}

if (rd0_broadcast_v==1)
	{for (i=0;i<16;i++)
		{
		if(rs[i]==rd0_broadcast)
			temp_rstag[i]=0;
		if(rt[i]==rd0_broadcast)
			temp_rttag[i]=0;
		}
	}
// updating branch_tag

if (branch_decision ==1)
	{for(i=0;i<15;i++)
		{if (branch_count[i]==br_count+1)
			temp_branchtag[i]=0;
		}
	}

free(insta);
free(instb);

}

void reg()
{

inst *instA, *instB;
instA = (inst*)malloc(sizeof(inst));
instB = (inst*)malloc(sizeof(inst));
breakup_inst(reg_inst_A, instA);
breakup_inst(reg_inst_B, instB);
//does nothing really....just there to maintain the order of events
free(instA);
free(instB);
}

void execute()
{
inst *instA, *instB;
instA = (inst *)malloc(sizeof(inst));
instB = (inst *)malloc(sizeof(inst));
breakup_inst(ex_inst_A, instA);
/*generate branch dec if instA is branch inst*/
if(instA->opcode == 4)//branch
{
		if(0 == (regfile[instA->rs] - regfile[instA->rt]))
			branch_decA = 1;
		else
			branch_decA = 2;
			br_count = branch_countA;
}
else
	branch_decA = 0;
/*generate branch dec if instB is branch inst*/
if(mem_o_hazard)
{
	breakup_inst(ex_inst_B, instB);
	if(instB->opcode == 4)//branch
		{
			if(0 == (regfile[instB->rs] - regfile[instB->rt]))
				branch_decB = 1;
			else
				branch_decB = 2;
			br_count = branch_countB;
		}
	else
		branch_decB = 0;
if(instB->opcode == 0x23 || instB->opcode == 0x28 || instB->opcode == 0x2b || instB->opcode == 0x20)
	memory_inst = 1;
else 
	memory_inst = 0;
}
free(instA);
free(instB);
}

void memB()
{
inst *instB;
instB = (inst *)malloc(sizeof(inst));
breakup_inst(mem_inst_B, instB);

//---------generating test outputs----------------
switch (instB->opcode)
{
case 0x23:
	{
	ports_o_test.mem_o_data = 0;
	ports_o_test.mem_o_addr = instB->imm_extended;
	ports_o_test.mem_o_dest_reg = instB->rt;
	ports_o_test.mem_o_ctrl = 3; //2'b11
	ports_o_test.mem_o_line_valid = 15;
	AppendNode(&headn, instB->rt, 3, 15);
	break;
	}

case 0x2b:
	{
	ports_o_test.mem_o_data = regfile[instB->rt];
	ports_o_test.mem_o_addr = instB->imm_extended;
	ports_o_test.mem_o_dest_reg = 0;
	ports_o_test.mem_o_ctrl = 2; //2'b10
	ports_o_test.mem_o_line_valid = 15;
	break;
	}
	
case 0x20:
	{
	ports_o_test.mem_o_data = 0;
	ports_o_test.mem_o_addr = instB->imm_extended;
	ports_o_test.mem_o_dest_reg = instB->rt;
	ports_o_test.mem_o_ctrl = 3; //2'b11
	ports_o_test.mem_o_line_valid = 1;
	AppendNode(&headn, instB->rt, 3, 1);
	break;
	}

case 0x28:
	{
	ports_o_test.mem_o_data = 0;
	ports_o_test.mem_o_addr = instB->imm_extended;
	ports_o_test.mem_o_dest_reg = instB->rt;
	ports_o_test.mem_o_ctrl = 2; //2'b10
	ports_o_test.mem_o_line_valid = 1;
	break;
	}
	
default:
	ports_o_test.mem_o_data = 0;
	ports_o_test.mem_o_addr = 0;
	ports_o_test.mem_o_dest_reg = 0;
	ports_o_test.mem_o_ctrl = 0; 
	ports_o_test.mem_o_line_valid = 0;
	break;
}
free(instB);
}

void write_back()
{
inst *instA, *instB;
instA = (inst *)malloc(sizeof(inst));
instB = (inst *)malloc(sizeof(inst));
breakup_inst(wb_inst_A, instA);
dec_ex_wb(instA);

breakup_inst(wb_inst_B, instB);

//---------------for pipeline B------------------

if(mem_o_hazard == 1) 
	{
	if(valid_mem == 15)
		{
			regfile[ports_i.mem_i_dest_m] = ((ports_i.mem_i_line & 0x1111111100000000) >>32);
			scoreboard[ports_i.mem_i_dest_m] = 0;
		}
	else
		if(valid_mem == 1)
			{
				regfile[ports_i.mem_i_dest_m] = ((ports_i.mem_i_line & 0x0000001100000000) >>32);
				scoreboard[ports_i.mem_i_dest_m] = 0;
			}
	}
else
	{
		dec_ex_wb(instB);
	}

if(ports_i.mem_i_line_valid_m == 15 && ports_i.mem_i_line_valid_m == 1 && ports_i.mem_i_ctrl_m)
	{
		mem_o_hazard = 1;
		valid_mem = ports_i.mem_i_line_valid_m;
	}
free(instA);
free(instB);
}

void commit()
{

wb_inst_A = ex_inst_A;
ex_inst_A = reg_inst_A;
reg_inst_A = issue_inst_A;

if(mem_o_hazard == 0)
{
	if(memory_inst)
	{
		wb_inst_B = mem_inst_B;
		mem_inst_B = ex_inst_B;
	}
	else 
	{
		wb_inst_B = ex_inst_B;
		mem_inst_B = 0;
	}
	ex_inst_B = reg_inst_B;
	reg_inst_B = issue_inst_B;
}

}
//-------util function---------------

void breakup_inst(int inst_32, inst* inst_struct)
{
int sign;

inst_struct->rs = inst_32 & 0x03e00000;
inst_struct->rt = inst_32 & 0x001f0000;
inst_struct->rd = inst_32 & 0x0000f800;
inst_struct->opcode = inst_32 & 0xf3000000;
inst_struct->funct = inst_32 & 0x0000003f;
sign = inst_32 & 0x00008000;
	  
if(sign)
	inst_struct->imm_extended = (inst_32 & 0x0000ffff) | (0xffff0000);
else
	inst_struct->imm_extended = (inst_32 & 0x0000ffff) & (0x0000ffff);

}

void dec_ex_wb(inst* inst_struct)
{
/*write back only if its an alu op  i.e for add, addi, nor*/
if(inst_struct->opcode == 0 && inst_struct->funct == 0x20) //add
	{
		regfile[inst_struct->rd] = regfile[inst_struct->rs] + regfile[inst_struct->rt];
		scoreboard[inst_struct->rd] = 0;
	}
if(inst_struct->opcode == 0 && inst_struct->funct == 0x27) //nor
	{
		regfile[inst_struct->rd] = ~(regfile[inst_struct->rs] | regfile[inst_struct->rt]);
		scoreboard[inst_struct->rd] = 0;
	}
if(inst_struct->opcode == 8)//addi
	{
		regfile[inst_struct->rt] = regfile[inst_struct->rs] + inst_struct->imm_extended;
		scoreboard[inst_struct->rt] = 0;
	}
}

void AppendNode(struct node** headRef, int reg_dest_in, int ctrl_i, int valid_i) 
{
   struct node* current = *headRef;
   struct node* newNode;
   newNode = malloc(sizeof(struct node));
   newNode->reg_dest = reg_dest_in;
   newNode->ctrl = ctrl_i;
   newNode->valid = valid_i;
   newNode->next = NULL;
   // special case for length 0
   if (current == NULL) {
      *headRef = newNode;
   }
   else {
      // Locate the last node
      while (current->next != NULL) {
          current = current->next;
      }
      current->next = newNode;
   }
}

void Pop(struct node** headRef, int *reg_dest, int *ctrl, int  *valid) {
   struct node* header;
   header = *headRef;
  
   if(header != NULL);
   {
   
   *reg_dest = header->reg_dest;
   *ctrl = header->ctrl;
   *valid = header->valid;
   
   *headRef = header->next;      
   
   free(header);           // free the head node
   }
}
//====================================================================================
void compare(int *error_list)
{

if(ports_o.inst_o_valid == ports_o_test.inst_o_valid)
	error_list[0] = 1;
	
if(ports_o.inst_o_addr == ports_o_test.inst_o_addr)
	error_list[1] = 1;
	
if(ports_o.mem_o_data == ports_o_test.mem_o_data)
	error_list[2] = 1;

if(ports_o.mem_o_addr == ports_o_test.mem_o_addr)
	error_list[3] = 1;

if(ports_o.mem_o_dest_reg == ports_o_test.mem_o_dest_reg)
	error_list[4] = 1;

if(ports_o.mem_o_ctrl == ports_o_test.mem_o_ctrl)
	error_list[5] = 1;

if(ports_o.mem_o_line_valid == ports_o_test.mem_o_line_valid)
	error_list[6] = 1;


}

void set_reset()
{
	if (rand() % 100 < 1000 * params.density_reset)
		ports_i.reset = 0;
	else
		ports_i.reset = 1;
}

void set_inst_i_valid()
{
 
}
/*
0x03fff820 = add
0x03fff8e7 = nor 
0x23ffffff = addi
0x8fffffff = lw
0xafffffff = sw
0x13ffffff = beq
0x0bffffff = jump
0x83ffffff = lb
0xa3ffffff = sb
*/

void set_inst_i_line()
{
int opcode_funct[] = {0x03fff820, 0x03fff8e7, 0x23ffffff,0x8fffffff,0xafffffff,0x13ffffff, 0x0bffffff, 0x83ffffff, 0xa3ffffff};
    int op_f_sel = 0;
    
    op_f_sel = rand() % 8;
    u64 lower_32 =0;
    lower_32 = (rand()) & opcode_funct[op_f_sel];
    
    op_f_sel = rand() % 8;
    u64 upper_32 =0;
    upper_32 = (rand()) & opcode_funct[op_f_sel];
    
    ports_i.inst_i_line = ((upper_32<<32)|lower_32);  
    

}
	
void set_mem_i_all()
{
int mem_return = 0;

if(mem_return)
	{
	 	Pop(&headn, &ports_i.mem_i_dest_m,&ports_i.mem_i_ctrl_m,&ports_i.mem_i_ctrl_m); 
		ports_i.mem_i_line = rand();
	}
}


